﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Navigation;

namespace UltimateNavigationFramework.UriNavigation
{
    public class UltimateNavigationJournal : IUltimateNavigationJournal
    {
        #region Private members
        private readonly object _syncLock = new object();
        private Stack<UltimateNavigationJournalEntry> _forwardStack = new Stack<UltimateNavigationJournalEntry>();
        private Stack<UltimateNavigationJournalEntry> _backStack = new Stack<UltimateNavigationJournalEntry>();
        private UltimateNavigationJournalEntry _currentEntry;
        private bool _suppressNavigationEvent;
        //private EventHandler<NavigationStateChangedEventArgs> _weakRefEventHandler;
        private EventHandler<UltimateNavigationJournalEventArgs> _navigated;  
        #endregion       

        #region Properties
        public bool CanGoBack
        {
            get
            {
                return this._backStack.Count > 0;
            }
        }

        public bool CanGoForward
        {
            get
            {
                return this._forwardStack.Count > 0;
            }
        }

        public UltimateNavigationJournalEntry CurrentEntry
        {
            get
            {
                return this._currentEntry;
            }
        }

        public Stack<UltimateNavigationJournalEntry> BackStack
        {
            get
            {
                return this._backStack;
            }
        }

        public Stack<UltimateNavigationJournalEntry> ForwardStack
        {
            get
            {
                return this._forwardStack;
            }
        }

        public event EventHandler<UltimateNavigationJournalEventArgs> Navigated
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this._navigated = this._navigated + value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this._navigated = this._navigated - value;
            }
        } 
        #endregion

        #region Constructor and destructor
        /// <summary>
        /// Constructor
        /// </summary>
        public UltimateNavigationJournal()
        {
            
        }        
        #endregion

        #region Add History Point
        /// <summary>
        /// Adds a new point into the journal
        /// </summary>
        /// <param name="journalEntry"></param>
        public void AddHistoryPoint(UltimateNavigationJournalEntry journalEntry)
        {
            if (journalEntry == null)
                throw new ArgumentNullException("journalEntry");

            lock (this._syncLock)
            {
                this._forwardStack.Clear();

                if (this._currentEntry != null)
                    this._backStack.Push(this._currentEntry);

                this._currentEntry = journalEntry;
            }

            this.UpdateObservables(journalEntry, NavigationMode.New);
            this.UpdateNavigationState(this.CurrentEntry);
        }

        /// <summary>
        /// Adds a new point into the journal if it is different
        /// </summary>
        /// <param name="journalEntry"></param>
        public void AddHistoryPointIfDifferent(UltimateNavigationJournalEntry journalEntry, bool suppressNavigation = false)
        {
            if (journalEntry == null)
                throw new ArgumentNullException("journalEntry");

            string b = string.Empty;

            if (this.CurrentEntry != null && this.CurrentEntry.Source != (Uri)null)
                b = UltimateNavigationFramework.Helpers.UriParser.InternalUriFromExternalValue(this.CurrentEntry.Source.OriginalString);

            if (string.Equals(journalEntry.Source.OriginalString, b, StringComparison.Ordinal))
                return;

            this._suppressNavigationEvent = suppressNavigation;
            this.AddHistoryPoint(journalEntry);
            this._suppressNavigationEvent = false;
        }

        /// <summary>
        /// Only add if it is different
        /// </summary>
        /// <param name="newState"></param>
        private void AddHistoryPointIfDifferent(string newState)
        {
            string b = string.Empty;

            if (this.CurrentEntry != null && this.CurrentEntry.Source != (Uri)null)
                b = UltimateNavigationFramework.Helpers.UriParser.InternalUriFromExternalValue(this.CurrentEntry.Source.OriginalString);

            if (string.Equals(newState, b, StringComparison.Ordinal))
                return;

            this._suppressNavigationEvent = true;
            this.AddHistoryPoint(new UltimateNavigationJournalEntry(string.Empty, new Uri(newState, UriKind.RelativeOrAbsolute)));
            this._suppressNavigationEvent = false;
        } 
        #endregion

        #region Go Back and Forward
        /// <summary>
        /// Go Back
        /// </summary>
        public void GoBack()
        {
            if (!this.CanGoBack)
            {
                throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, "Cannot go back", new object[1]
                {
                    (object) "CanGoBack"
                }));
            }
            else
            {
                lock (this._syncLock)
                {
                    this._forwardStack.Push(this._currentEntry);
                    this._currentEntry = this._backStack.Pop();
                }

                this.UpdateObservables(this._currentEntry, NavigationMode.Back);
                this.UpdateNavigationState(this.CurrentEntry);
            }
        }

        /// <summary>
        /// Go Forward
        /// </summary>
        public void GoForward()
        {
            if (!this.CanGoForward)
            {
                throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, "Cannot go forward", new object[1]
                {
                  (object) "CanGoForward"
                }));
            }
            else
            {
                lock (this._syncLock)
                {
                    this._backStack.Push(this._currentEntry);
                    this._currentEntry = this._forwardStack.Pop();
                }

                this.UpdateObservables(this._currentEntry, NavigationMode.Forward);
                this.UpdateNavigationState(this.CurrentEntry);
            }
        } 
        #endregion

        #region Add Entry
        /// <summary>
        /// Fire on navigated
        /// </summary>
        /// <param name="currentEntry"></param>
        /// <param name="mode"></param>
        private void UpdateObservables(UltimateNavigationJournalEntry currentEntry, NavigationMode mode)
        {
            this.OnNavigated(currentEntry.Name, currentEntry.Source, mode);
        }

        /// <summary>
        /// Fire on navigated event
        /// </summary>
        /// <param name="name"></param>
        /// <param name="uri"></param>
        /// <param name="mode"></param>
        protected void OnNavigated(string name, Uri uri, NavigationMode mode)
        {
            EventHandler<UltimateNavigationJournalEventArgs> eventHandler = this._navigated;
            if (eventHandler == null)
                return;

            UltimateNavigationJournalEventArgs e = new UltimateNavigationJournalEventArgs(name, uri, mode);

            eventHandler((object)this, e);
        } 

        /// <summary>
        /// Updates the Navigation State
        /// </summary>
        /// <param name="journalEntry"></param>
        private void UpdateNavigationState(UltimateNavigationJournalEntry journalEntry)
        {
            if (this._suppressNavigationEvent)
                return;

            string str = journalEntry.Source == (Uri)null ?
                string.Empty : UltimateNavigationFramework.Helpers.UriParser.InternalUriToExternalValue(journalEntry.Source);

            if (HtmlPage.IsEnabled)
            {
                if (UltimateNavigationJournal.UsingIFrame())
                {
                    HtmlPage.Document.SetProperty("title", (object)journalEntry.Name);
                    Application.Current.Host.NavigationState = str;
                }
                else
                {
                    Application.Current.Host.NavigationState = str;
                    HtmlPage.Document.SetProperty("title", (object)journalEntry.Name);
                }
            }
            else
                Application.Current.Host.NavigationState = str;
        } 
        #endregion

        #region Others
        /// <summary>
        /// Checks if it is using and IFrame
        /// </summary>
        /// <returns></returns>
        private static bool UsingIFrame()
        {
            string userAgent = HtmlPage.BrowserInformation.UserAgent;
            if (userAgent.Contains("MSIE 6.0") || userAgent.Contains("MSIE 7.0"))
                return true;
            else
                return false;
        }
        #endregion
    }
}
